package com.lskj.shiro.config.shiroconfig;

import com.lskj.shiro.config.jwtconfig.JWTToken;
import com.lskj.shiro.constant.Constant;
import com.lskj.shiro.entity.SysUser;
import com.lskj.shiro.enums.ExceptionEnum;
import com.lskj.shiro.service.ISysUserService;
import com.lskj.shiro.util.JWTUtil;
import com.lskj.shiro.util.RedisUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
 * @Author zzq
 * @Date 2020/3/30 23:34
 * @Version 1.0
 */
@Component
public class ShiroRealm extends AuthorizingRealm {

    private final static Logger logger = LoggerFactory.getLogger(ShiroRealm.class);

    @Autowired
    @Lazy
    private ISysUserService sysUserService;
    @Autowired
    @Lazy
    private RedisUtil redisUtil;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return Optional.ofNullable(principalCollection).map(principal -> {
            SysUser user = (SysUser) principal.getPrimaryPrincipal();
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user.getRoles().forEach(e -> simpleAuthorizationInfo.addRole(e.getRoleName()));
            Set<String> permission = new HashSet<>();
            user.getRoles().forEach(e -> {
                e.getPerms().forEach(e1 -> permission.add(e1.getPermName()));
            });
            simpleAuthorizationInfo.addStringPermissions(permission);
            return simpleAuthorizationInfo;
        }).orElse(new SimpleAuthorizationInfo());
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        return Optional.ofNullable(token).map(e-> {
            SysUser sysUser = this.checkUserTokenIsEffect(e);
            return new SimpleAuthenticationInfo(sysUser, e, getName());
        }).orElseThrow(()->new AuthenticationException(ExceptionEnum.TOKEN_NULL.getMessage()));
    }

    private SysUser checkUserTokenIsEffect(String token) throws AuthenticationException {
        String username = JWTUtil.getUserNameFromToken(token);
        if (username == null) {
            throw new AuthenticationException(ExceptionEnum.TOKEN_INVALID.getMessage());
        }

        SysUser sysUser = sysUserService.getUserByUserName(username);
        if (sysUser == null) {
            throw new AuthenticationException(ExceptionEnum.USER_UNEXIST.getMessage());
        }

        if (!jwtTokenRefresh(token, username, sysUser.getPassword())) {
            throw new AuthenticationException(ExceptionEnum.TOKEN_UNEXPIRE.getMessage());
        }

        if (sysUser.getStatus() != 1) {
            throw new AuthenticationException(ExceptionEnum.USER_INVALID.getMessage());
        }
        return sysUser;
    }

    private boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = (String) redisUtil.get(token);
        return Optional.ofNullable(cacheToken).map(e -> {
            if (!JWTUtil.verify(token, userName, passWord)) {
                String newAuthorization = JWTUtil.sign(userName, passWord);
                redisUtil.set(token, newAuthorization);
                redisUtil.expire(token, Constant.REDIS_KEY_EXPIRE);
            } else {
                redisUtil.set(token, e);
                redisUtil.expire(token, Constant.REDIS_KEY_EXPIRE);
            }
            return true;
        }).orElse(false);
    }
}
